/*
 * ThreadsMap.java
 *
 * Copyright 2007, 2008 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Lailaps.
 *
 * Tiberiumlabs Lailaps is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Lailaps is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.tiberiumlabs.lailaps.threads;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SwingUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tiberiumlabs.lailaps.actions.ActionFactory;
import org.tiberiumlabs.lailaps.actions.events.DataEvent;
import org.tiberiumlabs.lailaps.list.DownloadItem;
import org.tiberiumlabs.lailaps.list.StatusConstants;
import org.tiberiumlabs.lailaps.settings.Settings;
import org.tiberiumlabs.lailaps.threads.single.SingleStreamDownloadController;
import static javax.swing.SwingWorker.StateValue.PENDING;

/**
 *
 * @author <a href="mailto:paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public abstract class ThreadsMap implements StatusConstants {
    
    // <editor-fold defaultstate="collapsed" desc=" log messages ">
    private static final String WORKER_CREATED = "[ THREADS ] new worker created for url: ";
    private static final String WORKER_REMOVED = "[ THREADS ] worker removed for url: ";
    private static final String WORKER_REMOVED_FROM_QUEUE = "[ THREADS ] queue removed for url: ";
    // </editor-fold>
    
    private final HashMap<String, SingleStreamDownloadController> threadsMap = new HashMap<String, SingleStreamDownloadController>();
    private static final Log log = LogFactory.getLog(ThreadsMap.class);
    
    private static ThreadPoolExecutor executor = null;
    public static void execute(Runnable worker) {
        if (executor == null) {
            int maxWorkingThreads = Settings.getNetworkSettings().getMaxWorkingThreads();
            executor = new ThreadPoolExecutor(maxWorkingThreads, maxWorkingThreads, 0,
                    TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), new DownloadThreadFactory());
            log.info("[ THREADS ] new executor created...");
        }
        executor.execute(worker);
    }
    public static void setMaxWorkingThreads(int maxWorkingThreads) {
        if (executor != null) {
            executor.setCorePoolSize(maxWorkingThreads);
            executor.setMaximumPoolSize(maxWorkingThreads);
        }
    }
    
    public DownloadController create(DownloadItem item) {
        SingleStreamDownloadController worker = threadsMap.get(item.getUrl());
        if (worker == null) {
            worker = new SingleStreamDownloadController(item);
            threadsMap.put(item.getUrl(), worker);
        }
        log.info(WORKER_CREATED + item.getUrl()); // TODO do we need url?
        return worker;
    }
    
    public DownloadController get(DownloadItem item) {
        return threadsMap.get(item.getUrl());
    }
    
    public void remove(DownloadItem item) {
        SingleStreamDownloadController worker = threadsMap.get(item.getUrl());
        boolean removedFromQueue = false;
        if (worker != null) {
            worker.stop();
            threadsMap.remove(item.getUrl());
            removedFromQueue = executor.remove(worker);
        }
        log.info((removedFromQueue ? WORKER_REMOVED_FROM_QUEUE : WORKER_REMOVED) + item.getUrl());
    }
    
    public void done(DownloadItem item) {
        threadsMap.remove(item.getUrl());
        // TODO log messages
    }
    
    public void notify(final DataEvent event) {
        if (SwingUtilities.isEventDispatchThread()) {
            ActionFactory.execute(new Runnable() {
                @Override
                public void run() {
                    notifyNow(event);
                }
            });
            return;
        }
        notifyNow(event);
    }
    
    private void notifyNow(DataEvent event) {
        Iterator<SingleStreamDownloadController> iterator = threadsMap.values().iterator();
        while (iterator.hasNext()) {
            iterator.next().dataChanged(event);
        }
    }
    
    public void shutdown() {
        String exception = "exception while shutting down download threads";
        // TODO check what to do - shutdown or shutdownNow
        if (executor != null) {
            int count = 0;
            executor.shutdown();
            ArrayList<SingleStreamDownloadController> list = new ArrayList<SingleStreamDownloadController>(threadsMap.values());
            count = list.size();
            for (int i = list.size()-1; i >= 0; i--) {
                list.get(i).stop();
            }
            
            try {
                executor.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                log.warn(exception, ex);
            }
            
            log.info("[ THREADS ] shutdown complete, stopped threads: " + count + ", still working threads: " + executor.getActiveCount());
        }
    }
    
    private static class DownloadThreadFactory implements ThreadFactory {
        final AtomicInteger poolNumber = new AtomicInteger(1);
        final ThreadGroup group;
        final AtomicInteger threadNumber = new AtomicInteger(1);
        final String namePrefix;
        
        DownloadThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null)? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
            namePrefix = "Lailaps-download-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }
        
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != 3) {
                t.setPriority(3);
            }
            return t;
        }
    }
    
}
